home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-04-16 | 15.6 KB | 338 lines | [TEXT/R*ch] |
- unit Floating;
-
- {A set of routines to handle floating windows. For use with THINK Pascal 4.0}
- {Written by F. Pottier, june 1993. E-mail : pottier@clipper.ens.fr}
-
- {Originally based on a set of C routines written by Patrick Doane (America OnLine : Patrick5) }
- {Thanks go to him, and also to Troy Gaul for placing his Infinity Windoid in the public domain}
-
- {This code is placed in the public domain. It may be used by anybody, for any purposes. It would be kind to give credits to me}
- {as well as to the aforementioned people, though.}
- {If you experience any problems, or have ideas for enhancements, tell me about it. I can't guarantee I'll spend much}
- {time on it, since I'm not a professional programmer, but I'll take a look at it.}
-
- { --- Some general comments ------------------------------------------------------------------------------------------ }
-
- {The Window Manager doesn't know about floating windows. To obtain floating windows in a program,}
- {one has to handle them 'by hand', which means calling low-level Window Manager routines to place the windows}
- {in their correct positions after each event.}
- {The frequently used, and now to be avoided, Window Manager routines are :}
- { SelectWindow }
- { CloseWindow }
- { HideWindow }
- { ShowWindow }
- { DisposeWindow }
- { DragWindow }
- {Those routines don't know about floating windows, so calling them would put the floating windows behind normal windows.}
- {You also have to be careful with GetNewWindow. You can't call GetNewWindow(ID, wStorage, Pointer(-1)) because that}
- {would put the new window in the front. Rather, we call GetNewWindow(ID, wStorage, nil) which places the window in the}
- {back, and then call our home-made routine, SelectTheWindow.}
-
- {At every point in the program, we maintain three global variables : topFloat, bottomFloat and topWindow.}
- {topFloat holds the top floating window, nil if there is none. Same thing for bottomFloat with the bottom floating window,}
- {and for topWindow with the top regular window. These three global variables, along with the Window Manager's window}
- {list, are enough to handle the behavior of our windows.}
- {Hidden windows are placed behind all others, so they don't count when determining those variables' values.}
-
- {In order to distinguish between floating windows and regular ones, we choose a different windowKind for the former.}
-
- {To make hiliting and unhiliting floating windows simpler, I chose to use a WDEF that always draws windoids in a hilited}
- {state (e.g. Infinity Windoid with the Always Hilite option). This way, I don't have to worry at all about it.}
-
- {A final remark about modeless dialogs : modeless dialogs and floating windows don't go well together. The Dialog Manager}
- {is confused by floating windows. IsDialogEvent and DialogSelect don't work because they use FrontWindow to find the dialog.}
- {It is easy to rewrite a modified version of isDialogEvent. It is already trickier for DialogSelect. Maybe it would be simpler}
- {to use windows with controls instead of modeless dialogs. Or maybe it's possible to have DialogSelect and isDialogEvent}
- {work by patching FrontWindow ? }
- {If somebody comes up with a good idea on this topic, please tell me about it.}
-
- { --- How to use the unit, in short -------------------------------------------------------------------------------------- }
-
- {- Call InitFloats once before opening any windows.}
- {- To create a new window, call GetNewWindow(id, storage, NIL). Then call SelectTheWindow if it's a regular window,}
- { otherwise call MakeFloat.}
- {- To determine whether a given window is a floating one, call isFloating}
- {- Instead of SelectWindow[and ShowWindow], HideWindow, DisposeWindow, DragWindow, use SelectTheWindow, HideTheWindow,}
- { DIsposeTheWindow and DragTheWindow.}
- {- Upon a receiving a suspend event, you may call HideFloats. Use ShowFloats when receiving a resume event.}
- {- Instead of calling FrontWindow, you can read the values of the variables topFloat and topWindow. They should be up-to-date}
- { at any point in the program.}
-
- { --- Unit interface --------------------------------------------------------------------------------------------------- }
-
- interface
-
- procedure InitFloats; {initializes the global variables}
-
- function IsFloating (whichWindow: WindowPtr): Boolean; {tells whether a window is floating}
- procedure MakeFloat (theWindow: WindowPtr); {turns a newly created window into a floating window}
-
- procedure HideTheWindow (whichWindow: WindowPtr); {equivalents for the old Toolbox calls}
- procedure DisposeTheWindow (whichWindow: WindowPtr);
- procedure SelectTheWindow (whichWindow: WindowPtr);
- procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
- procedure DisposeTheDialog (whichDialog: DialogPtr);
-
- procedure HideFloats; {hides all floating windows}
- procedure ShowFloats; {shows all floating windows}
-
- var
- bottomFloat, topFloat, topWindow: WindowPtr;
- hasColorQD: Boolean; {initialized by InitFloats}
-
- { -------------------------------------------------------------------------------------------------------------------- }
-
- implementation
-
- const
- kFloatingKind = 317; {arbitrary constant for floating windows' windowKind}
-
- {ActivateWindow hilites/unhilites the window and then generates an activate/deactivate event for it.}
- {In order to generate activate events, it mucks with low-mem globals. It would be cleaner to directly call the activate}
- {routine for the window. The advantage of this method is that it also works with modeless dialogs : the Dialog Manager}
- {doesn't know that a dialog is active unless it actually receives an activate event.}
-
- procedure ActivateWindow (window: WindowPtr; on: Boolean);
- const
- CurActivate = $0A64; {writing a WindowPtr in those globals generates}
- CurDeactive = $0A68; {an activate or deactivate event}
- var
- p: ^WindowPtr;
- begin
- if window <> nil then begin
- HiliteWindow(window, on); {hilite the window}
- if on then
- p := Pointer(CurActivate) {generate the appropriate event}
- else
- p := Pointer(CurDeactive);
- p^ := window;
- end;
- end;
-
- {InitFloats is to be called at the beginning of the program, before creating any windows. It sets our global variables to nil.}
-
- procedure InitFloats;
- var
- e: OSErr;
- response: longint;
- begin
- topFloat := nil;
- bottomFloat := nil;
- topWindow := nil;
-
- e := Gestalt(gestaltQuickdrawVersion, response);
- hasColorQD := (e = noErr) and (response >= gestalt8bitQD);
- end;
-
- {IsFloating and isVisible are little, foolprof routines that tell whether a window is floating or visible.}
- {Using them avoids errors such as examining WindowPeek(whichWindow)^.visible when whichWindow = nil}
-
- function IsFloating (whichWindow: WindowPtr): Boolean;
- begin
- if whichWindow = nil then
- IsFloating := false
- else
- IsFloating := WindowPeek(whichWindow)^.windowKind = kFloatingKind;
- end;
-
- function isVisible (whichWindow: WindowPtr): Boolean;
- begin
- if whichWindow = nil then
- isVisible := false
- else
- isVisible := WindowPeek(whichWindow)^.visible;
- end;
-
- {UpdateFloats updates the topFloat and bottomFloat variables by walking down the window list. It then determines}
- {topWindow by taking the first visible window after bottomFloat.}
-
- procedure UpdateVars;
- var
- theWindow: WindowPtr;
- begin
- theWindow := FrontWindow; {start from the frontmost window}
- if IsFloating(theWindow) then begin {if it's floating, then it's topFloat}
- topFloat := theWindow;
- while (theWindow <> nil) and isFloating(theWindow) do begin {walk down the list until we find a regular window}
- bottomFloat := theWindow; {or the end of the list}
- theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
- end;
-
- while (theWindow <> nil) and (not isVisible(theWindow)) do {find the next visible window after bottomFloat}
- theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
- topWindow := theWindow;
- end
- else begin {if the frontmost window is a regular one, then there}
- topFloat := nil; {are no floating windows and topWindow = FrontWindow}
- bottomFloat := nil;
- topWindow := FrontWindow;
- end;
- end;
-
- {MakeFloat turns a newly created window into a floating window and brings it to the front}
- {The window must have been created via a call to [Get]NewWindow(id, wStorage, nil).}
-
- procedure MakeFloat (theWindow: WindowPtr);
- begin
- BringToFront(theWindow); {Bring the window to the front without unhiliting other}
- ShowHide(theWindow, true); {windows. Make it visible in case it isn't}
- if topFloat = nil then
- bottomFloat := theWindow; {update bottomFloat et topFloat}
- topFloat := theWindow;
- WindowPeek(theWindow)^.windowKind := kFloatingKind; {remember that this window should float}
- end;
-
- {ReactToRemoval handles a few necessary steps after killing whichWindow. underWindow is the window that was}
- {under whichWindow (i.e. next to it in the window list).}
-
- procedure ReactToRemoval (underWindow: WindowPeek);
- begin
- while (underWindow <> nil) and (not isVisible(WindowPtr(underWindow))) do
- underWindow := underWindow^.nextWindow; {activate the next visible window under whichWindow}
- ActivateWindow(WindowPtr(underWindow), true);
- UpdateVars; {update the variables}
- end;
-
- {There was a bug in HideTheWindow, reported and fixed by Ralph ? (I stupidly lost his name and address). The problem was that}
- {HideWindow(whichWindow) moves the next visible window to the top of the list, so ReactToRemoval will end up activating the}
- {second visible window, and we will have two active windows. The fix is to call SendBehind to send whichWindow behind all}
- {other windows prior to hiding it. This way HideWindow has no side effects.}
-
- procedure HideTheWindow (whichWindow: WindowPtr);
- var
- underWindow: WindowPeek; {we must determine which window is under whichWindow}
- begin {before hiding it, because HideWindow sends it to the back}
- underWindow := WindowPeek(whichWindow)^.nextWindow; {thus changing the value of nextWindow}
- SendBehind(whichWindow, nil);
- HideWindow(whichWindow);
- ReactToRemoval(underWindow);
- end;
-
- procedure DisposeTheWindow (whichWindow: WindowPtr);
- var
- underWindow: WindowPeek;
- begin
- underWindow := WindowPeek(whichWindow)^.nextWindow; {same thing as HideTheWindow, with DisposeWindow instead}
- DisposeWindow(whichWindow);
- ReactToRemoval(underWindow);
- end;
-
- procedure DisposeTheDialog (whichDialog: DialogPtr);
- var
- underWindow: WindowPeek;
- begin
- underWindow := WindowPeek(whichDialog)^.nextWindow; {same thing as HideTheWindow, with DisposeDialog instead}
- DisposeDialog(whichDialog);
- ReactToRemoval(underWindow);
- end;
-
- {SelectTheWindow is the most important routine. Here are the steps to be taken :}
- {If whichWindow is not visible, show it.}
- {- If whichWindow is a floating window, just bring it to the front.}
- {- If it is a regular window and there are no floating windows, bring it to the front, activate it, deactivate the former front window}
- {- If it is a regular window and there are floating windows, send it behind bottomFloat, activate it, deactivate the old topWindow}
- {Finally, update the global variables.}
-
- procedure SelectTheWindow (whichWindow: WindowPtr);
- var
- wPeek: WindowPeek;
- begin
- wPeek := WindowPeek(whichWindow);
-
- if not wPeek^.visible then {make the window visible if necessary}
- ShowHide(whichWindow, true);
-
- if IsFloating(whichWindow) then {a floating window just needs to be brought to the front}
- BringToFront(whichWindow)
- else if whichWindow <> topWindow then begin {regular window. If it isn't topWindow we have to react}
- if bottomFloat <> nil then begin {if there are floating windows, then send whichWindow}
- SendBehind(whichWindow, bottomFloat); {behind bottomFloat}
- PaintOne(wPeek, wPeek^.strucRgn); {These two low-level calls are necessary to generate}
- CalcVis(wPeek); {update events and maintain the windows' visRgns}
- end
- else
- BringToFront(whichWindow); {no floating windows : just call BringToFront}
-
- ActivateWindow(whichWindow, true); {activate the new front window}
- ActivateWindow(topWindow, false); {and deactivate the old one}
- end;
-
- UpdateVars; {finally update our vars}
- end;
-
- {DragTheWindow is to be called instead of DragWindow, because DragWindow brings the window to the front unless the command}
- {key is down. We use DragGrayRgn to drag a gray outline of the window. We clip the dragging to a region consisting of the whole}
- {desktop, minus the windows that are in front of the window we're dragging.}
- {To emulate DragWindow's behavior, we send the window to the front, unless the command key is down.}
-
- procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
- const
- cancelDrag = $80008000; {returned by DragGrayRgn if the user aborts the drag}
- var
- savePort: GrafPtr;
- wPort: GrafPort;
- thePoint: point;
- newLoc: longint;
- theWindow: WindowPeek;
- dragRect: rect;
- dragRgn: RgnHandle;
- begin
- if BitAnd(event.modifiers, cmdKey) = 0 then {unless the command key is depressed, select the window}
- SelectTheWindow(whichWindow); {before doing the dragging}
-
- GetPort(savePort); {save the grafport}
- SetPort(whichWindow);
- thePoint := whichWindow^.portRect.topLeft;
- LocalToGlobal(thePoint); {remember the window's original position}
-
- dragRgn := NewRgn; {put the window's strucRgn in a new region}
- CopyRgn(WindowPeek(whichWindow)^.strucRgn, dragRgn); {we can't pass the strucRgn directly to DragGrayRgn}
- {because DragGrayRgn modifies the region}
- OpenPort(@wPort); {open a port to draw the gray outline}
- CopyRgn(GetGrayRgn, wPort.visRgn); {set its visRgn to the whole screen, minus the strucRgns}
- theWindow := WindowPeek(FrontWindow); {of all the windows in front of whichWindow}
- while theWindow <> WindowPeek(whichWindow) do begin
- DiffRgn(wPort.visRgn, theWindow^.strucRgn, wPort.visRgn);
- theWindow := theWindow^.nextWindow;
- end;
-
- dragRect := GetGrayRgn^^.rgnBBox;
- InsetRect(dragRect, 4, 4);
-
- newLoc := DragGrayRgn(dragRgn, event.where, dragRect, dragRect, noConstraint, nil);
- if (newLoc <> cancelDrag) and (newLoc <> 0) then
- MoveWindow(whichWindow, thePoint.h + LoWord(newLoc), thePoint.v + HiWord(newLoc), false);
-
- DisposeRgn(dragRgn); {dispose of the region and of the port}
- ClosePort(@wPort);
- SetPort(savePort); {then restore the old grafPort and exit}
- end;
-
- {HideFloats/ShowFloats are simple routines that hide/show all floating windows. They're useful to handle suspend/resume events.}
-
- procedure HideFloats;
- begin
- while isFloating(FrontWindow) do {calling FrontWindow twice is not very efficient, but}
- HideWindow(FrontWindow); {it's shorter!}
- topFloat := nil;
- bottomFloat := nil;
- end;
-
- procedure ShowFloats;
- const
- WindowList = $09D6; {this low-mem variable contains the address of the}
- var {first window in the Window Manager's list}
- theWindow, nextWindow: WindowPeek;
- p: ^WindowPeek;
- begin
- p := Pointer(WindowList);
- theWindow := WindowPeek(p^); {find the first window in the Window Manager's list}
- while (theWindow <> nil) do begin {walk down the list...}
- nextWindow := theWindow^.nextWindow;
- if isFloating(WindowPtr(theWindow)) then
- SelectTheWindow(WindowPtr(theWindow)); {..sending every floating window to the front}
- theWindow := nextWindow;
- end;
- end;
-
- end.